/*-----------------------------------
Music.h
Code by liyongchao, iwinyeah@163.com
-----------------------------------*/

#include "music.h"

Music::Music(uint8_t pin)
{
    tonePin = pin;
}

void Music::turnOn(bool on)
{
    if(enable && !on)
    {
        reset();
        toneStart = false;
    }
    enable = on;
}

void Music::start(const uint8_t *list, bool force)
{
    if (enable && (force || !toneStart)) // 若强制开始或演奏未开始
    {
        toneList = (uint8_t *)list;
        reset();
    }
}

void Music::step()
{
    if (enable && toneStart && toneTicks) //若演奏已开始且节拍未完成
    {
        toneTicks--;
        if (toneTicks == 0) // 当前节拍刚完成，读取序列下一命令
        {
            next();
        }
        else
        {
            if (noteSlide) // 处理滑音
            {
                noteNo += noteSlide;
                // 1-127为midi音符，查表得到频率，128以上直接表示频率(须-128)
                tone(tonePin, noteNo >= 128 ? noteNo - 128 : _midi_tables[noteNo]);
            }
        }
    }
}

void Music::next()
{
    for (;;)
    {
        uint8_t cmd = toneList[toneIndex++];
        switch (cmd & 0xE0) // 只取前3位(bit7-5)
        {
        case 0: // 0~7
        {
            switch (cmd)
            {
            case M_END: // 结束命令
                noTone(tonePin);
                toneStart = false;
                return;
            case M_REWIND: // 重播(我创造的命令:>)
                reset();
                return;
            case M_DELAY: // 延时命令: 下一字节是延时节拍数
                toneTicks = toneList[toneIndex++];
                return;
            case M_WAVE:
            case M_ENVELOPE:
            case M_SLIDE:
            case M_VOLUME:
            {
                uint8_t p0 = toneList[toneIndex++];
                uint8_t mask = p0;
                p0 >>= 4;
                for (uint8_t i = 0, m = 1; i < 4; i++, m <<= 1)
                {
                    if (m & mask)
                    {
                        switch (cmd)
                        {
                        case M_WAVE: // 波形(不能处理此类命令)
                            break;
                        case M_ENVELOPE: // 包络(不能处理此类命令)
                            break;
                        case M_SLIDE: // 滑音
                            if (i == channel) // 与当前声音通道一致才有效
                            {
                                noteSlide = toneList[toneIndex];
                            }
                            toneIndex++;
                            break;
                        case M_VOLUME: // 音量(不能处理此类命令)
                            toneIndex++;
                            break;
                        }
                    }
                }
                break;
            }
            break;
            default:
                return;
            }
        }
        break;

        case M_DELAY_N: // M_DELAY_N, M_DELAY_1~M_DELAY_N_30, 0~30 延时命令，bit5-0是延时节拍数
            toneTicks = cmd & 0x1F;
            return;
        case M_NOTES_OFF: // 停止发声命令，bit5-0是声音通道
            for (uint8_t i = 0, m = 1; i < 4; i++, m <<= 1)
            {
                if (m & cmd)
                {
                    if (i == channel) // 与当前声音通道一致才有效
                    {
                        noTone(tonePin);
                        channel = 0xff; // 停止后复位当前通道以使其它通道可用
                    }
                }
            }
            break;
        case M_NOTES_ON: // 发声命令，bit5-0是声音通道，下一字节是发出的音调(使用midi定义的编号)
        {
            for (uint8_t i = 0, m = 1; i < 4; i++, m <<= 1)
            {
                if (m & cmd)
                {
                    if (0xff == channel || i == channel) // 未指定通道或与当前声音通道一致
                    {
                        noteNo = toneList[toneIndex];
                        tone(tonePin, noteNo >= 128 ? noteNo - 128 : _midi_tables[noteNo]);
                        channel = i;
                    }
                    toneIndex++;
                }
            }
        }
        break;
        default:
            return;
        }
    }
}

void Music::reset()
{
    noTone(tonePin);

    toneIndex = 0;
    channel = 0xff;

    noteNo = 0;
    noteSlide = 0;

    toneTicks = 1;

    toneStart = true;
}